#ifndef __BASIC_MATH_H__
#define __BASIC_MATH_H__

#include <cmath>

namespace basic {
template <typename T>
struct tpl_T2 {
  tpl_T2() : x(T(0)), y(T(0)) {
  }
  tpl_T2(const T& v) : x(v), y(v) {
  }
  tpl_T2(const T& _x, const T& _y) : x(_x), y(_y) {
  }
  tpl_T2(const tpl_T2<T>& v) : x(v.x), y(v.y) {
  }
  tpl_T2<T>& operator=(const tpl_T2<T>& v) {
    x = v.x;
    y = v.y;
    return *this;
  }
  tpl_T2<T>& operator+=(const tpl_T2<T>& v) {
    x += v.x;
    y += v.y;
    return *this;
  }
  tpl_T2<T>& operator+=(const T& v) {
    x += v;
    y += v;
    return *this;
  }
  tpl_T2<T> operator+(const tpl_T2<T>& v) {
    return tpl_T2<T>(x + v.x, y + v.y);
  }
  tpl_T2<T> operator+(const T& v) {
    return tpl_T2<T>(x + v, y + v);
  }
  tpl_T2<T>& operator-=(const tpl_T2<T>& v) {
    x -= v.x;
    y -= v.y;
    return *this;
  }
  tpl_T2<T>& operator-=(const T& v) {
    x -= v;
    y -= v;
    return *this;
  }
  tpl_T2<T> operator-(const tpl_T2<T>& v) {
    return tpl_T2<T>(x - v.x, y - v.y);
  }
  tpl_T2<T> operator-(const T& v) {
    return tpl_T2<T>(x - v, y - v);
  }
  tpl_T2<T>& operator*=(const tpl_T2<T>& v) {
    x *= v.x;
    y *= v.y;
    return *this;
  }
  tpl_T2<T>& operator*=(const T& v) {
    x *= v;
    y *= v;
    return *this;
  }
  tpl_T2<T> operator*(const tpl_T2<T>& v) {
    return tpl_T2<T>(x * v.x, y * v.y);
  }
  tpl_T2<T> operator*(const T& v) {
    return tpl_T2<T>(x * v, y * v);
  }
  tpl_T2<T>& operator/=(const tpl_T2<T>& v) {
    x /= v.x;
    y /= v.y;
    return *this;
  }
  tpl_T2<T>& operator/=(const T& v) {
    x /= v;
    y /= v;
    return *this;
  }
  tpl_T2<T> operator/(const tpl_T2<T>& v) {
    return tpl_T2<T>(x / v.x, y / v.y);
  }
  tpl_T2<T> operator/(const T& v) {
    return tpl_T2<T>(x / v, y / v);
  }
  tpl_T2<T>& Set0() {
    x = T(0);
    y = T(0);
    return *this;
  }
  tpl_T2<T>& Set(const T& _x, const T& _y) {
    x = _x;
    y = _y;
    return *this;
  }
  tpl_T2<T>& Set(const T& v) {
    x = v;
    y = v;
    return *this;
  }
  tpl_T2<T>& Add(const tpl_T2<T>& v) {
    x += v.x;
    y += v.y;
    return *this;
  }
  tpl_T2<T>& Add(const T& _x, const T& _y) {
    x += _x;
    y += _y;
    return *this;
  }
  tpl_T2<T>& Add(const T& v) {
    x += v;
    y += v;
    return *this;
  }
  T Dot(const tpl_T2<T>& v) {
    return x * v.x + y * v.y;
  }
  inline T MagnitudeSquared() const {
    return x * x + y * y;
  }
  inline float Magnitude() const {
    return T(sqrt((float)MagnitudeSquared()));
  }
  tpl_T2<T>& Normalize() {
    float _invMag = 1.0 / Magnitude();
    x = T(x * _invMag);
    y = T(y * _invMag);
    return *this;
  }
  T* Data() {
    return &x;
  }
  const T* Data() const {
    return &x;
  }
  union {
    T x, u;
  };
  union {
    T y, v;
  };
};

template <typename T>
struct tpl_T3 {
  tpl_T3() : x(T(0)), y(T(0)), z(T(0)) {
  }
  tpl_T3(const T& v) : x(v), y(v), z(v) {
  }
  tpl_T3(const T& _x, const T& _y, const T& _z) : x(_x), y(_y), z(_z) {
  }
  tpl_T3(const tpl_T3<T>& v) : x(v.x), y(v.y), z(v.z) {
  }
  tpl_T3<T>& operator=(const tpl_T3<T>& v) {
    x = v.x;
    y = v.y;
    z = v.z;
    return *this;
  }
  tpl_T3<T>& operator+=(const tpl_T3<T>& v) {
    x += v.x;
    y += v.y;
    z += v.z;
    return *this;
  }
  tpl_T3<T>& operator+=(const T& v) {
    x += v;
    y += v;
    z += v;
    return *this;
  }
  tpl_T3<T> operator+(const tpl_T3<T>& v) {
    return tpl_T3<T>(x + v.x, y + v.y, z + v.z);
  }
  tpl_T3<T> operator+(const T& v) {
    return tpl_T3<T>(x + v, y + v, z + v);
  }
  tpl_T3<T>& operator-=(const tpl_T3<T>& v) {
    x -= v.x;
    y -= v.y;
    z -= v.z;
    return *this;
  }
  tpl_T3<T>& operator-=(const T& v) {
    x -= v;
    y -= v;
    z -= v;
    return *this;
  }
  tpl_T3<T> operator-(const tpl_T3<T>& v) {
    return tpl_T3<T>(x - v.x, y - v.y, z - v.z);
  }
  tpl_T3<T> operator-(const T& v) {
    return tpl_T3<T>(x - v, y - v, z - v);
  }
  tpl_T3<T>& operator*=(const tpl_T3<T>& v) {
    x *= v.x;
    y *= v.y;
    z *= v.z;
    return *this;
  }
  tpl_T3<T>& operator*=(const T& v) {
    x *= v;
    y *= v;
    z *= v;
    return *this;
  }
  tpl_T3<T> operator*(const tpl_T3<T>& v) {
    return tpl_T3<T>(x * v.x, y * v.y, z * v.z);
  }
  tpl_T3<T> operator*(const T& v) {
    return tpl_T3<T>(x * v, y * v, z * v);
  }
  tpl_T3<T>& operator/=(const tpl_T3<T>& v) {
    x /= v.x;
    y /= v.y;
    z /= v.z;
    return *this;
  }
  tpl_T3<T>& operator/=(const T& v) {
    x /= v;
    y /= v;
    z /= v;
    return *this;
  }
  tpl_T3<T> operator/(const tpl_T3<T>& v) {
    return tpl_T3<T>(x / v.x, y / v.y, z / v.z);
  }
  tpl_T3<T> operator/(const T& v) {
    return tpl_T3<T>(x / v, y / v, z / v);
  }
  tpl_T3<T>& Set0() {
    x = T(0);
    y = T(0);
    z = T(0);
    return *this;
  }
  tpl_T3<T>& Set(const T& _x, const T& _y, const T& _z) {
    x = _x;
    y = _y;
    z = _z;
    return *this;
  }
  tpl_T3<T>& Set(const T& v) {
    x = v;
    y = v;
    z = v;
    return *this;
  }
  tpl_T3<T>& Add(const tpl_T3<T>& v) {
    x += v.x;
    y += v.y;
    z += v.z;
    return *this;
  }
  tpl_T3<T>& Add(const T& _x, const T& _y, const T& _z) {
    x += _x;
    y += _y;
    z += _z;
    return *this;
  }
  tpl_T3<T>& Add(const T& v) {
    x += v;
    y += v;
    z += v;
    return *this;
  }
  T Dot(const tpl_T3<T>& v) {
    return x * v.x + y * v.y + z * v.z;
  }
  inline T MagnitudeSquared() const {
    return x * x + y * y + z * z;
  }
  inline float Magnitude() const {
    return T(sqrt((float)MagnitudeSquared()));
  }
  tpl_T3<T>& Normalize() {
    float _invMag = 1.0 / Magnitude();
    x = T(x * _invMag);
    y = T(y * _invMag);
    z = T(z * _invMag);
    return *this;
  }
  T* Data() {
    return &x;
  }
  const T* Data() const {
    return &x;
  }
  union {
    T x, r, roll;
  };
  union {
    T y, g, pitch;
  };
  union {
    T z, b, yaw;
  };
};

template <typename T>
struct tpl_T4 {
  tpl_T4() : x(T(0)), y(T(0)), z(T(0)), w(T(0)) {
  }
  explicit tpl_T4(const T& v) : x(v), y(v), z(v), w(z) {
  }
  tpl_T4(const T& _x, const T& _y, const T& _z, const T& _w) : x(_x), y(_y), z(_z), w(_w) {
  }
  tpl_T4(const tpl_T4<T>& v) : x(v.x), y(v.y), z(v.z), w(v.w) {
  }
  tpl_T4<T>& operator=(const tpl_T4<T>& v) {
    x = v.x;
    y = v.y;
    z = v.z;
    w = v.w;
    return *this;
  }
  tpl_T4<T>& operator+=(const tpl_T4<T>& v) {
    x += v.x;
    y += v.y;
    z += v.z;
    w += v.w;
    return *this;
  }
  tpl_T4<T>& operator+=(const T& v) {
    x += v;
    y += v;
    z += v;
    w += v;
    return *this;
  }
  tpl_T4<T> operator+(const tpl_T4<T>& v) {
    return tpl_T4<T>(x + v.x, y + v.y, z + v.z, w + v.w);
  }
  tpl_T4<T> operator+(const T& v) {
    return tpl_T4<T>(x + v, y + v, z + v, w + v);
  }
  tpl_T4<T>& operator-=(const tpl_T4<T>& v) {
    x -= v.x;
    y -= v.y;
    z -= v.z;
    w -= v.w;
    return *this;
  }
  tpl_T4<T>& operator-=(const T& v) {
    x -= v;
    y -= v;
    z -= v;
    w -= v;
    return *this;
  }
  tpl_T4<T> operator-(const tpl_T4<T>& v) {
    return tpl_T4<T>(x - v.x, y - v.y, z - v.z, w - v.w);
  }
  tpl_T4<T> operator-(const T& v) {
    return tpl_T4<T>(x - v, y - v, z - v, w - v);
  }
  tpl_T4<T>& operator*=(const tpl_T4<T>& v) {
    x *= v.x;
    y *= v.y;
    z *= v.z;
    w *= v.w;
    return *this;
  }
  tpl_T4<T>& operator*=(const T& v) {
    x *= v;
    y *= v;
    z *= v;
    w *= v;
    return *this;
  }
  tpl_T4<T> operator*(const tpl_T4<T>& v) {
    return tpl_T4<T>(x * v.x, y * v.y, z * v.z, w * v.w);
  }
  tpl_T4<T> operator*(const T& v) {
    return tpl_T4<T>(x * v, y * v, z * v, w * v);
  }
  tpl_T4<T>& operator/=(const tpl_T4<T>& v) {
    x /= v.x;
    y /= v.y;
    z /= v.z;
    w /= v.w;
    return *this;
  }
  tpl_T4<T>& operator/=(const T& v) {
    x /= v;
    y /= v;
    z /= v;
    w /= v;
    return *this;
  }
  tpl_T4<T> operator/(const tpl_T4<T>& v) {
    return tpl_T4<T>(x / v.x, y / v.y, z / v.z, w / v.w);
  }
  tpl_T4<T> operator/(const T& v) {
    return tpl_T4<T>(x / v, y / v, z / v, w / v);
  }
  tpl_T4<T>& Set0() {
    x = T(0);
    y = T(0);
    z = T(0);
    w = T(0);
    return *this;
  }
  tpl_T4<T>& Set(const T& _x, const T& _y, const T& _z, const T& _w) {
    x = _x;
    y = _y;
    z = _z;
    w = _w;
    return *this;
  }
  tpl_T4<T>& Set(const T& v) {
    x = v;
    y = v;
    z = v;
    w = v;
    return *this;
  }
  tpl_T4<T>& Add(const tpl_T4<T>& v) {
    x += v.x;
    y += v.y;
    z += v.z;
    w += v.w;
    return *this;
  }
  tpl_T4<T>& Add(const T& _x, const T& _y, const T& _z, const T& _w) {
    x += _x;
    y += _y;
    z += _z;
    w += _w;
    return *this;
  }
  tpl_T4<T>& Add(const T& v) {
    x += v;
    y += v;
    z += v;
    w += v;
    return *this;
  }
  T Dot(const tpl_T4<T>& v) {
    return x * v.x + y * v.y + z * v.z + w * v.w;
  }
  inline T MagnitudeSquared() const {
    return x * x + y * y + z * z + w * w;
  }
  inline float Magnitude() const {
    return T(sqrt((float)MagnitudeSquared()));
  }
  tpl_T4<T>& Normalize() {
    float _invMag = 1.0 / Magnitude();
    x = T(x * _invMag);
    y = T(y * _invMag);
    z = T(z * _invMag);
    w = T(w * _invMag);
    return *this;
  }
  T* Data() {
    return &x;
  }
  const T* Data() const {
    return &x;
  }
  union {
    T x, r;
  };
  union {
    T y, g;
  };
  union {
    T z, b;
  };
  union {
    T w, a;
  };
};

template <typename T>
struct tpl_Matrix2 {};

template <typename T>
struct tpl_Matrix3 {};

template <typename T>
struct tpl_Matrix4 {
  tpl_Matrix4() : row1(), row2(), row3(), row4() {
  }
  tpl_Matrix4(const tpl_T4<T>& r1, const tpl_T4<T>& r2, const tpl_T4<T>& r3, const tpl_T4<T>& r4)
      : row1(r1), row2(r2), row3(r3), row4(r4) {
  }
  tpl_Matrix4(T a, T b, T c, T d, T e, T f, T g, T h, T i, T j, T k, T l, T m, T n, T o, T p)
      : row1(a, b, c, d), row2(e, f, g, h), row3(i, j, k, l), row4(m, n, o, p) {
  }
  tpl_Matrix4(const tpl_Matrix4<T>& m) : row1(m.row1), row2(m.row2), row3(m.row3), row4(m.row4) {
  }

  tpl_Matrix4<T>& operator=(const tpl_Matrix4<T>& m) {
    row1.Set(m.row1);
    row2.Set(m.row2);
    row3.Set(m.row3);
    row4.Set(m.row4);
    return *this;
  }
  tpl_Matrix4<T>& operator+=(const tpl_Matrix4<T>& m) {
    row1.x += m.row1.x;
    row1.y += m.row1.y;
    row1.z += m.row1.z;
    row1.w += m.row1.w;
    row2.x += m.row2.x;
    row2.y += m.row2.y;
    row2.z += m.row2.z;
    row2.w += m.row2.w;
    row3.x += m.row3.x;
    row3.y += m.row3.y;
    row3.z += m.row3.z;
    row3.w += m.row3.w;
    row4.x += m.row4.x;
    row4.y += m.row4.y;
    row4.z += m.row4.z;
    row4.w += m.row4.w;
    return *this;
  }
  tpl_Matrix4<T> operator+(const tpl_Matrix4<T>& m) {
    tpl_Matrix4<T> _out(*this);
    _out += m;
    return _out;
  }
  tpl_Matrix4<T>& operator-=(const tpl_Matrix4<T>& m) {
    row1.x -= m.row1.x;
    row1.y -= m.row1.y;
    row1.z -= m.row1.z;
    row1.w -= m.row1.w;
    row2.x -= m.row2.x;
    row2.y -= m.row2.y;
    row2.z -= m.row2.z;
    row2.w -= m.row2.w;
    row3.x -= m.row3.x;
    row3.y -= m.row3.y;
    row3.z -= m.row3.z;
    row3.w -= m.row3.w;
    row4.x -= m.row4.x;
    row4.y -= m.row4.y;
    row4.z -= m.row4.z;
    row4.w -= m.row4.w;
    return *this;
  }
  tpl_Matrix4<T> operator-(const tpl_Matrix4<T>& m) {
    tpl_Matrix4<T> _out(*this);
    _out -= m;
    return _out;
  }
  tpl_Matrix4<T>& operator*=(const tpl_Matrix4<T>& m) {
    row1.x *= m.row1.x;
    row1.y *= m.row1.y;
    row1.z *= m.row1.z;
    row1.w *= m.row1.w;
    row2.x *= m.row2.x;
    row2.y *= m.row2.y;
    row2.z *= m.row2.z;
    row2.w *= m.row2.w;
    row3.x *= m.row3.x;
    row3.y *= m.row3.y;
    row3.z *= m.row3.z;
    row3.w *= m.row3.w;
    row4.x *= m.row4.x;
    row4.y *= m.row4.y;
    row4.z *= m.row4.z;
    row4.w *= m.row4.w;
    return *this;
  }
  tpl_Matrix4<T> operator*(const tpl_Matrix4<T>& m) {
    tpl_Matrix4<T> _out(*this);
    _out *= m;
    return _out;
  }
  tpl_Matrix4<T>& operator/=(const tpl_Matrix4<T>& m) {
    row1.x /= m.row1.x;
    row1.y /= m.row1.y;
    row1.z /= m.row1.z;
    row1.w /= m.row1.w;
    row2.x /= m.row2.x;
    row2.y /= m.row2.y;
    row2.z /= m.row2.z;
    row2.w /= m.row2.w;
    row3.x /= m.row3.x;
    row3.y /= m.row3.y;
    row3.z /= m.row3.z;
    row3.w /= m.row3.w;
    row4.x /= m.row4.x;
    row4.y /= m.row4.y;
    row4.z /= m.row4.z;
    row4.w /= m.row4.w;
    return *this;
  }
  tpl_Matrix4<T> operator/(const tpl_Matrix4<T>& m) {
    tpl_Matrix4<T> _out(*this);
    _out /= m;
    return _out;
  }

  tpl_Matrix4<T>& Set0() {
    row1.Set0();
    row2.Set0();
    row3.Set0();
    row4.Set0();
    return *this;
  }
  tpl_Matrix4<T>& SetE() {
    Set0();
    row1.x = T(1);
    row2.y = T(1);
    row3.z = T(1);
    row4.w = T(1);
    return *this;
  }
  tpl_Matrix4<T>& Set(T a, T b, T c, T d, T e, T f, T g, T h, T i, T j, T k, T l, T m, T n, T o, T p) {
    row1.Set(a, b, c, d);
    row2.Set(e, f, g, h);
    row3.Set(i, j, k, l);
    row4.Set(m, n, o, p);
    return *this;
  }
  tpl_Matrix4<T>& Set(const tpl_Matrix4<T>& m) {
    row1.Set(m.row1);
    row2.Set(m.row2);
    row3.Set(m.row3);
    row4.Set(m.row4);
    return *this;
  }
  tpl_T4<T> Multiply(const tpl_T4<T>& v) {
    tpl_T4<T> _out;
    _out.x = row1.x * v.x + row1.y * v.y + row1.z * v.z + row1.w * v.w;
    _out.y = row2.x * v.x + row2.y * v.y + row2.z * v.z + row2.w * v.w;
    _out.z = row3.x * v.x + row3.y * v.y + row3.z * v.z + row3.w * v.w;
    _out.w = row4.x * v.x + row4.y * v.y + row4.z * v.z + row4.w * v.w;
    return _out;
  }
  tpl_Matrix4<T> Multiply(const tpl_Matrix4<T>& m) {
    tpl_Matrix4<T> _out;
    _out.row1.x = row1.x * m.row1.x + row1.y * m.row2.x + row1.z * m.row3.x + row1.w * m.row4.x;
    _out.row1.y = row1.x * m.row1.y + row1.y * m.row2.y + row1.z * m.row3.y + row1.w * m.row4.y;
    _out.row1.z = row1.x * m.row1.z + row1.y * m.row2.z + row1.z * m.row3.z + row1.w * m.row4.z;
    _out.row1.w = row1.x * m.row1.w + row1.y * m.row2.w + row1.z * m.row3.w + row1.w * m.row4.w;

    _out.row2.x = row2.x * m.row1.x + row2.y * m.row2.x + row2.z * m.row3.x + row2.w * m.row4.x;
    _out.row2.y = row2.x * m.row1.y + row2.y * m.row2.y + row2.z * m.row3.y + row2.w * m.row4.y;
    _out.row2.z = row2.x * m.row1.z + row2.y * m.row2.z + row2.z * m.row3.z + row2.w * m.row4.z;
    _out.row2.w = row2.x * m.row1.w + row2.y * m.row2.w + row2.z * m.row3.w + row2.w * m.row4.w;

    _out.row3.x = row3.x * m.row1.x + row3.y * m.row2.x + row3.z * m.row3.x + row3.w * m.row4.x;
    _out.row3.y = row3.x * m.row1.y + row3.y * m.row2.y + row3.z * m.row3.y + row3.w * m.row4.y;
    _out.row3.z = row3.x * m.row1.z + row3.y * m.row2.z + row3.z * m.row3.z + row3.w * m.row4.z;
    _out.row3.w = row3.x * m.row1.w + row3.y * m.row2.w + row3.z * m.row3.w + row3.w * m.row4.w;

    _out.row4.x = row4.x * m.row1.x + row4.y * m.row2.x + row4.z * m.row3.x + row4.w * m.row4.x;
    _out.row4.y = row4.x * m.row1.y + row4.y * m.row2.y + row4.z * m.row3.y + row4.w * m.row4.y;
    _out.row4.z = row4.x * m.row1.z + row4.y * m.row2.z + row4.z * m.row3.z + row4.w * m.row4.z;
    _out.row4.w = row4.x * m.row1.w + row4.y * m.row2.w + row4.z * m.row3.w + row4.w * m.row4.w;
    return _out;
  }
  tpl_Matrix4<T>& MultiplyToSelf(const tpl_Matrix4<T>& m) {
    tpl_Matrix4<T> _tmp = Multiply(m);
    Set(_tmp);
    return *this;
  }
  tpl_Matrix4<T>& Transpose() {
    tpl_Matrix4<T> _tmp(row1.x, row2.x, row3.x, row4.x, row1.y, row2.y, row3.y, row4.y, row1.z, row2.z, row3.z, row4.z,
                        row1.w, row2.w, row3.w, row4.w);
    Set(_tmp);
    return *this;
  }
  T* Data() {
    return &row1.x;
  }
  const T* Data() const {
    return &row1.x;
  }
  tpl_T4<T> row1, row2, row3, row4;
};
} // namespace basic

#endif
